﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ShareFile.Api.Client.Primitives
{
    /// <summary>
    /// A compact representation of a ShareFile unique identifier.
    /// </summary>
    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 4)]
    public struct ShareFileId : IEquatable<ShareFileId>
    {
        private readonly Guid guid;
        private readonly string unparsedId;
        private readonly Data data;

        public ShareFileId(string sfId) : this(sfId, mustParse: false) { }

        internal ShareFileId(string sfId, bool mustParse)
        {
            unparsedId = sfId;
            data = default(Data);
            guid = default(Guid);
            if (string.IsNullOrEmpty(sfId))
            {
                return;
            }

            var constructor = new Constructor(sfId);
            bool parseSucceeded;
            if (mustParse)
            {
                constructor.Parse();
                parseSucceeded = true;
            }
            else
            {
                parseSucceeded = constructor.TryParse();
            }

            if (parseSucceeded)
            {
                data = constructor.Data;
                guid = constructor.Guid;
                unparsedId = null;
            }
        }

        private bool UseUnparsedId => data == default(Data) && guid == default(Guid);

        public override string ToString()
        {
            if (UseUnparsedId)
            {
                return unparsedId;
            }
            string prefix = data.Prefix;
            string guidFormat = data.GuidIncludesHyphens ? "D" : "N";
            string guidString = guid.ToString(guidFormat);
            int guidDigitsWanted = data.TotalLength - prefix.Length;
            if (data.GuidSubstringHigh)
            {
                guidString = guidString.Substring(0, guidDigitsWanted);
            }
            else
            {
                guidString = guidString.Substring(guidString.Length - guidDigitsWanted);
            }
            return prefix + guidString;
        }

        #region equality
        private StringComparer SfIdStringComparer => StringComparer.OrdinalIgnoreCase;
        private const StringComparison SfIdStringComparison = StringComparison.OrdinalIgnoreCase;

        public bool Equals(ShareFileId other)
        {
            if (UseUnparsedId && other.UseUnparsedId)
            {
                return string.Equals(unparsedId, other.unparsedId, SfIdStringComparison);
            }
            return guid == other.guid
                && data == other.data;
        }

        public static bool operator ==(ShareFileId left, ShareFileId right) => left.Equals(right);
        public static bool operator !=(ShareFileId left, ShareFileId right) => !left.Equals(right);

        public override int GetHashCode()
        {
            var hashCode = -203229721; // generated by visual studio
            hashCode = hashCode * -1521134295 + base.GetHashCode();
            hashCode = hashCode * -1521134295 + EqualityComparer<Guid>.Default.GetHashCode(guid);
            if (unparsedId != null)
            {
                hashCode = hashCode * -1521134295 + SfIdStringComparer.GetHashCode(unparsedId);
            }
            hashCode = hashCode * -1521134295 + EqualityComparer<Data>.Default.GetHashCode(data);
            return hashCode;
        }

        public bool Equals(string value)
        {
            var other = new ShareFileId(value);
            return Equals(other);
        }

        public override bool Equals(object obj)
        {
            if (obj is null)
            {
                return Equals(default(ShareFileId));
            }
            if (obj is ShareFileId other)
            {
                return Equals(other);
            }
            else if (obj is string s)
            {
                return Equals(s);
            }
            return false;
        }

        public static bool operator ==(ShareFileId left, object right) => left.Equals(right);
        public static bool operator !=(ShareFileId left, object right) => !left.Equals(right);
        public static bool operator ==(object left, ShareFileId right) => right == left;
        public static bool operator !=(object left, ShareFileId right) => right != left;
        #endregion

        #region string compatibility
        public static implicit operator ShareFileId(string sfId) => new ShareFileId(sfId);
        public static implicit operator string(ShareFileId id) => id.ToString();

        public char this[int index]
        {
            get
            {
                if (UseUnparsedId)
                {
                    return unparsedId[index];
                }
                string prefix = data.Prefix;
                if (index < prefix.Length)
                {
                    return prefix[index];
                }
                return ToString()[index];
            }
        }

        public int Length => UseUnparsedId ? unparsedId.Length : data.TotalLength;

        private static bool IsCaseSensitiveComparison(StringComparison comparisonType)
        {
            if (comparisonType == StringComparison.CurrentCulture
#if !PORTABLE && !NETSTANDARD1_3
                || comparisonType == StringComparison.InvariantCulture
#endif
                || comparisonType == StringComparison.Ordinal)
            {
                return true;
            }
            return false;
        }

        public bool Equals(string value, StringComparison comparisonType)
        {
            if (IsCaseSensitiveComparison(comparisonType))
            {
                return ToString().Equals(value, comparisonType);
            }
            return Equals(value);
        }

        public bool StartsWith(string value)
        {
            if (UseUnparsedId)
            {
                return unparsedId.StartsWith(value);
            }
            string prefix = data.Prefix;
            if (value != null && value.Length <= prefix.Length)
            {
                return prefix.StartsWith(value);
            }
            return ToString().StartsWith(value);
        }

        public bool StartsWith(string value, StringComparison comparisonType)
        {
            if (IsCaseSensitiveComparison(comparisonType))
            {
                return ToString().StartsWith(value, comparisonType);
            }
            return StartsWith(value);
        }

        public string Substring(int startIndex) => ToString().Substring(startIndex);

        public string Substring(int startIndex, int length)
        {
            if (UseUnparsedId)
            {
                return unparsedId.Substring(startIndex, length);
            }
            string prefix = data.Prefix;
            if (startIndex + length <= prefix.Length)
            {
                return prefix.Substring(startIndex, length);
            }
            return ToString().Substring(startIndex, length);
        }

        public object Clone() => this;
        public int CompareTo(string strB) => ToString().CompareTo(strB);
        public bool Contains(string value) => ToString().Contains(value);
        public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) => ToString().CopyTo(sourceIndex, destination, destinationIndex, count);
        public bool EndsWith(string value) => ToString().EndsWith(value);
        public bool EndsWith(string value, StringComparison comparisonType) => ToString().EndsWith(value, comparisonType);
        public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType) => ToString().IndexOf(value, startIndex, count, comparisonType);
        public int IndexOf(string value, int startIndex, StringComparison comparisonType) => ToString().IndexOf(value, startIndex, comparisonType);
        public int IndexOf(string value, int startIndex, int count) => ToString().IndexOf(value, startIndex, count);
        public int IndexOf(char value, int startIndex, int count) => ToString().IndexOf(value, startIndex, count);
        public int IndexOf(string value) => ToString().IndexOf(value);
        public int IndexOf(char value, int startIndex) => ToString().IndexOf(value, startIndex);
        public int IndexOf(char value) => ToString().IndexOf(value);
        public int IndexOf(string value, int startIndex) => ToString().IndexOf(value, startIndex);
        public int IndexOf(string value, StringComparison comparisonType) => ToString().IndexOf(value, comparisonType);
        public int IndexOfAny(char[] anyOf, int startIndex) => ToString().IndexOfAny(anyOf, startIndex);
        public int IndexOfAny(char[] anyOf) => ToString().IndexOfAny(anyOf);
        public int IndexOfAny(char[] anyOf, int startIndex, int count) => ToString().IndexOfAny(anyOf, startIndex, count);
        public string Insert(int startIndex, String value) => ToString().Insert(startIndex, value);
        public int LastIndexOf(string value) => ToString().LastIndexOf(value);
        public int LastIndexOf(string value, int startIndex) => ToString().LastIndexOf(value, startIndex);
        public int LastIndexOf(string value, int startIndex, int count) => ToString().LastIndexOf(value, startIndex, count);
        public int LastIndexOf(string value, StringComparison comparisonType) => ToString().LastIndexOf(value, comparisonType);
        public int LastIndexOf(string value, int startIndex, StringComparison comparisonType) => ToString().LastIndexOf(value, startIndex, comparisonType);
        public int LastIndexOf(string value, int startIndex, int count, StringComparison comparisonType) => ToString().LastIndexOf(value, startIndex, count, comparisonType);
        public int LastIndexOf(char value) => ToString().LastIndexOf(value);
        public int LastIndexOf(char value, int startIndex, int count) => ToString().LastIndexOf(value, startIndex, count);
        public int LastIndexOf(char value, int startIndex) => ToString().LastIndexOf(value, startIndex);
        public int LastIndexOfAny(char[] anyOf) => ToString().LastIndexOfAny(anyOf);
        public int LastIndexOfAny(char[] anyOf, int startIndex, int count) => ToString().LastIndexOfAny(anyOf, startIndex, count);
        public int LastIndexOfAny(char[] anyOf, int startIndex) => ToString().LastIndexOfAny(anyOf, startIndex);
        public string PadLeft(int totalWidth, char paddingChar) => ToString().PadLeft(totalWidth, paddingChar);
        public string PadLeft(int totalWidth) => ToString().PadLeft(totalWidth);
        public string PadRight(int totalWidth, char paddingChar) => ToString().PadRight(totalWidth, paddingChar);
        public string PadRight(int totalWidth) => ToString().PadRight(totalWidth);
        public string Remove(int startIndex, int count) => ToString().Remove(startIndex, count);
        public string Remove(int startIndex) => ToString().Remove(startIndex);
        public string Replace(char oldChar, char newChar) => ToString().Replace(oldChar, newChar);
        public string Replace(string oldValue, string newValue) => ToString().Replace(oldValue, newValue);
        public string[] Split(char[] separator, int count, StringSplitOptions options) => ToString().Split(separator, count, options);
        public string[] Split(string[] separator, StringSplitOptions options) => ToString().Split(separator, options);
        public string[] Split(string[] separator, int count, StringSplitOptions options) => ToString().Split(separator, count, options);
        public string[] Split(char[] separator, int count) => ToString().Split(separator, count);
        public string[] Split(params char[] separator) => ToString().Split(separator);
        public string[] Split(char[] separator, StringSplitOptions options) => ToString().Split(separator, options);
        public char[] ToCharArray() => ToString().ToCharArray();
        public char[] ToCharArray(int startIndex, int length) => ToString().ToCharArray(startIndex, length);
        public string ToLower() => ToString().ToLower();
        public string ToLowerInvariant() => ToString().ToLowerInvariant();
        public string ToUpper() => ToString().ToUpper();
        public string ToUpperInvariant() => ToString().ToUpperInvariant();
        public string Trim() => ToString().Trim();
        public string Trim(params char[] trimChars) => ToString().Trim(trimChars);
        public string TrimEnd(params char[] trimChars) => ToString().TrimEnd(trimChars);
        public string TrimStart(params char[] trimChars) => ToString().TrimStart(trimChars);

#if !PORTABLE && !NETSTANDARD1_3
        public int CompareTo(object value) => ToString().CompareTo(value);
        public bool EndsWith(string value, bool ignoreCase, System.Globalization.CultureInfo culture) => ToString().EndsWith(value, ignoreCase, culture);
        public CharEnumerator GetEnumerator() => ToString().GetEnumerator();
        public bool IsNormalized() => ToString().IsNormalized();
        public bool IsNormalized(NormalizationForm normalizationForm) => ToString().IsNormalized(normalizationForm);
        public string Normalize() => ToString().Normalize();
        public string Normalize(NormalizationForm normalizationForm) => ToString().Normalize(normalizationForm);
        public bool StartsWith(string value, bool ignoreCase, System.Globalization.CultureInfo culture) => ToString().StartsWith(value, ignoreCase, culture);
        public string ToLower(System.Globalization.CultureInfo culture) => ToString().ToLower(culture);
        public string ToString(IFormatProvider provider) => ToString().ToString(provider);
        public string ToUpper(System.Globalization.CultureInfo culture) => ToString().ToUpper(culture);
#endif
        #endregion

        #region data format
        /* DATA LAYOUT
         * 0,5: prefix char0
         * 5,5: prefix char1
         * 10,5: prefix char2
         * 15,5: prefix char3
         * 20,5: prefix char4
         * 25,1: guidToStringHyphens flag
         * 25,6: total length
         * 31,1: guidSubstringHigh flag */
        private const int MAX_PREFIX_CHAR_COUNT = 5;
        private const int PREFIX_CHAR_BITS = 5;

        private static bool PrefixCharOffset(int charIndex, bool mustParse, out int offset)
        {
            if (charIndex < 0 || charIndex >= MAX_PREFIX_CHAR_COUNT)
            {
                offset = 0;
                if (mustParse) throw new ArgumentOutOfRangeException(nameof(charIndex), $"Prefix char index allowed range 0-{MAX_PREFIX_CHAR_COUNT - 1}");
                return false;
            }
            offset = charIndex * PREFIX_CHAR_BITS;
            return true;
        }

        /* PREFIX CHAR MAPPING
         * 0: no value
         * 1-26: a-z, A-Z
         * 27: -
         * 28: _
         * 29: unused
         * 30-31: reserved for constant prefixes */

        // CONSTANT PREFIXES
        private const int NO_CONSTANT_PREFIX_ID = 0;
        private const string ALLSHARED = "allshared";
        private const int ALLSHARED_ID = 1;
        private const string FAVORITES = "favorites";
        private const int FAVORITES_ID = 2;
        private const string CONNECTORS = "connectors";
        private const int CONNECTORS_ID = 3;
        private const string CIFS = "c-cifs";
        private const int CIFS_ID = 4;
        private const string DOCUMENTUM = "c-documentum";
        private const int DOCUMENTUM_ID = 5;
        private const string SHARECONNECT = "c-shareconnect";
        private const int SHARECONNECT_ID = 6;

        private struct Data : IEquatable<Data>
        {
            public string Prefix => GetPrefixString();
            public bool GuidIncludesHyphens => GetGuidIncludesHyphens();
            public int TotalLength => GetTotalLength();
            public bool GuidSubstringHigh => GetGuidSubstringHigh();

            private readonly int data;

            public Data(int data)
            {
                this.data = data;
            }

            private int GetBitRange(int offset, int length)
            {
                int shift = 32 - (offset + length);
                int shifted = data >> shift;
                int mask = (1 << length) - 1;
                int masked = shifted & mask;
                return masked;
            }

            private static char? BitsToPrefixChar(int b)
            {
                if (b >= 1 && b <= 26)
                {
                    return (char)('a' + b - 1);
                }
                else if (b == 0)
                {
                    return null;
                }
                else if (b == 27)
                {
                    return '-';
                }
                else if (b == 28)
                {
                    return '_';
                }
                throw new ArgumentOutOfRangeException(nameof(b), $"Invalid prefix character value {b}");
            }

            private char? GetPrefixChar(int charIndex)
            {
                int offset;
                PrefixCharOffset(charIndex, true, out offset);
                int value = GetBitRange(offset, PREFIX_CHAR_BITS);
                return BitsToPrefixChar(value);
            }

            private int TryGetConstantPrefixId()
            {
                int prefixId = 0;
                for (int i = 0; i < MAX_PREFIX_CHAR_COUNT; i++)
                {
                    int offset;
                    PrefixCharOffset(i, true, out offset);
                    int val = GetBitRange(offset, PREFIX_CHAR_BITS);
                    if (val < 30)
                    {
                        return NO_CONSTANT_PREFIX_ID;
                    }
                    val &= 1;
                    int shifted = val << i;
                    prefixId |= shifted;
                }
                return prefixId;
            }

            private static string ConstantPrefixString(int prefixId)
            {
                if (prefixId > 31)
                {
                    throw new ArgumentException($"Invalid constant prefix id {prefixId} (maximum 31)");
                }
                switch (prefixId)
                {
                    case ALLSHARED_ID:
                        return ALLSHARED;
                    case FAVORITES_ID:
                        return FAVORITES;
                    case CONNECTORS_ID:
                        return CONNECTORS;
                    case CIFS_ID:
                        return CIFS;
                    case DOCUMENTUM_ID:
                        return DOCUMENTUM;
                    case SHARECONNECT_ID:
                        return SHARECONNECT;
                    default:
                        throw new ArgumentException($"Unknown constant prefix id {prefixId}");
                }
            }

            private string GetPrefixString()
            {
                int constantPrefixId = TryGetConstantPrefixId();
                if (constantPrefixId != NO_CONSTANT_PREFIX_ID)
                {
                    return ConstantPrefixString(constantPrefixId);
                }

                var sb = new StringBuilder(MAX_PREFIX_CHAR_COUNT);
                for (int i = 0; i < MAX_PREFIX_CHAR_COUNT; i++)
                {
                    char? c = GetPrefixChar(i);
                    if (!c.HasValue)
                    {
                        break;
                    }
                    sb.Append(c.Value);
                }
                return sb.ToString();
            }

            private bool GetGuidIncludesHyphens()
            {
                int value = GetBitRange(25, 1);
                return value == 1;
            }

            private int GetTotalLength()
            {
                if (data == 0)
                {
                    return 0;
                }
                return GetBitRange(25, 6) + 1;
            }

            private bool GetGuidSubstringHigh()
            {
                int value = GetBitRange(31, 1);
                return value == 1;
            }

            private int EqualityData()
            {
                int masked = data | 127; // compare only prefix characters for equality
                return masked;
            }

            public bool Equals(Data other) => EqualityData() == other.EqualityData();
            public static bool operator ==(Data left, Data right) => left.Equals(right);
            public static bool operator !=(Data left, Data right) => !left.Equals(right);
            public override int GetHashCode() => EqualityData().GetHashCode();
            public override bool Equals(object obj)
            {
                var other = obj as Data?;
                return other.HasValue ? Equals(other) : false;
            }
        }

        private struct Constructor
        {
            private readonly string sfId;

            private int data;
            public Data Data => new Data(data);
            public Guid Guid { get; private set; }
            private bool mustParse;

            private const int NOT_FOUND_INDEX = -1;

            public Constructor(string sfId)
            {
                this.sfId = sfId;
                data = 0;
                Guid = default(Guid);
                mustParse = false;
            }

            public bool TryParse()
            {
                mustParse = false;
                return ParseInternal();
            }

            public void Parse()
            {
                mustParse = true;
                ParseInternal();
            }

            private bool ParseInternal()
            {
                if (string.IsNullOrEmpty(sfId))
                {
                    if (mustParse) throw new ArgumentException($"Id must not be null or empty");
                    return false;
                }

                string prefix; int constantPrefixId;
                if (!GetPrefix(out prefix, out constantPrefixId)) return false;
                if (!PutPrefix(prefix, constantPrefixId)) return false;

                bool includesHyphens = sfId.IndexOf('-', startIndex: prefix.Length) != NOT_FOUND_INDEX;
                if (!PutGuidIncludesHyphens(includesHyphens)) return false;

                if (includesHyphens && sfId.Length <= 32)
                {
                    if (mustParse) throw new ArgumentException($"Id with hyphens '{sfId}' must be at least 33 characters");
                    return false;
                }
                if (!includesHyphens && sfId.Length > 32)
                {
                    if (mustParse) throw new ArgumentException($"Id without hyphens '{sfId}' must be 32 or fewer characters");
                    return false;
                }
                if (!PutTotalLength(sfId.Length)) return false;

                bool substringHigh;
                if (!ParseGuid(prefix.Length, includesHyphens, out substringHigh)) return false;
                if (!PutGuidSubstringHigh(substringHigh)) return false;

                return true;
            }

            private static bool IsGuidDigit(char c)
            {
                bool is09 = '0' <= c && c <= '9';
                bool isaf = 'a' <= c && c <= 'f';
                bool isAF = 'A' <= c && c <= 'F';
                bool isHyphen = c == '-';
                return is09 || isaf || isAF || isHyphen;
            }

            private static int TryGetConstantPrefixId(string sfId)
            {
                if (ALLSHARED.Equals(sfId, StringComparison.OrdinalIgnoreCase))
                    return ALLSHARED_ID;
                if (FAVORITES.Equals(sfId, StringComparison.OrdinalIgnoreCase))
                    return FAVORITES_ID;
                if (CONNECTORS.Equals(sfId, StringComparison.OrdinalIgnoreCase))
                    return CONNECTORS_ID;
                if (CIFS.Equals(sfId, StringComparison.OrdinalIgnoreCase))
                    return CIFS_ID;
                if (DOCUMENTUM.Equals(sfId, StringComparison.OrdinalIgnoreCase))
                    return DOCUMENTUM_ID;
                if (SHARECONNECT.Equals(sfId, StringComparison.OrdinalIgnoreCase))
                    return SHARECONNECT_ID;

                return NO_CONSTANT_PREFIX_ID;
            }

            private bool GetPrefix(out string prefix, out int constantPrefixId)
            {
                int prefixEndIndex = NOT_FOUND_INDEX;
                for (int i = 0; i < sfId.Length; i++)
                {
                    if (!IsGuidDigit(sfId[i]))
                    {
                        prefixEndIndex = i;
                    }
                }
                if (prefixEndIndex + 1 < sfId.Length && sfId[prefixEndIndex + 1] == '-')
                {
                    prefixEndIndex++;
                }
                if (prefixEndIndex >= MAX_PREFIX_CHAR_COUNT)
                {
                    constantPrefixId = TryGetConstantPrefixId(sfId);
                }
                else
                {
                    constantPrefixId = NO_CONSTANT_PREFIX_ID;
                }
                prefix = constantPrefixId == NO_CONSTANT_PREFIX_ID
                    ? sfId.Substring(0, prefixEndIndex + 1)
                    : sfId;
                return true;
            }

            private bool AssertValueSize(int value, int length)
            {
                int high = value >> length;
                if (high != 0)
                {
                    if (mustParse) throw new ArgumentException($"Value {value} too large for length {length}");
                    return false;
                }
                return true;
            }

            private bool PutBitRange(int value, int offset, int length)
            {
                if (!AssertValueSize(value, length)) return false;
                int shift = 32 - (offset + length);
                int shifted = value << shift;
                data = data | shifted;
                return true;
            }

            private bool PrefixCharToBits(char c, out int i)
            {
                if (c >= 'a' && c <= 'z')
                {
                    i = c - 'a' + 1;
                    return true;
                }
                else if (c >= 'A' && c <= 'Z')
                {
                    i = c - 'A' + 1;
                    return true;
                }
                else if (c == '-')
                {
                    i = 27;
                    return true;
                }
                else if (c == '_')
                {
                    i = 28;
                    return true;
                }
                else if (c == 0)
                {
                    i = 0;
                    return true;
                }
                i = 0;
                if (mustParse) throw new ArgumentOutOfRangeException(nameof(c), $"Invalid prefix character '{c}'");
                return false;
            }

            private bool PutConstantPrefix(int prefixId)
            {
                for (int i = 0; i < MAX_PREFIX_CHAR_COUNT; i++)
                {
                    int shifted = prefixId >> i;
                    int masked = shifted & 1;
                    int offset;
                    if (!PrefixCharOffset(i, mustParse, out offset)) return false;
                    if (!PutBitRange(30 + masked, offset, PREFIX_CHAR_BITS)) return false;
                }
                return true;
            }

            private bool PutPrefixChar(char c, int charIndex)
            {
                int offset;
                if (!PrefixCharOffset(charIndex, mustParse, out offset)) return false;
                int value;
                if (!PrefixCharToBits(c, out value)) return false;
                return PutBitRange(value, offset, PREFIX_CHAR_BITS);
            }

            private bool PutPrefix(string prefix, int constantPrefixId)
            {
                if (constantPrefixId != NO_CONSTANT_PREFIX_ID)
                {
                    return PutConstantPrefix(constantPrefixId);
                }
                if (prefix.Length > MAX_PREFIX_CHAR_COUNT)
                {
                    if (mustParse) throw new ArgumentException($"Id prefix '{prefix}' length exceeds maximum {MAX_PREFIX_CHAR_COUNT}");
                    return false;
                }
                for (int i = 0; i < prefix.Length; i++)
                {
                    if (!PutPrefixChar(prefix[i], i))
                        return false;
                }
                return true;
            }

            private bool PutGuidIncludesHyphens(bool b)
            {
                return PutBitRange(b ? 1 : 0, 25, 1);
            }

            private bool PutTotalLength(int length)
            {
                return PutBitRange(length - 1, 25, 6);
            }

            private bool PutGuidSubstringHigh(bool b)
            {
                return PutBitRange(b ? 1 : 0, 31, 1);
            }

            private bool ParseGuid(int prefixLength, bool includesHyphens, out bool substringHigh)
            {
                string idWithoutPrefix = sfId.Substring(prefixLength);
                int padGuidToLength = includesHyphens ? 36 : 32;

                string guidString = idWithoutPrefix.PadLeft(padGuidToLength, '0');
                Guid g;
                if (Guid.TryParse(guidString, out g))
                {
                    substringHigh = false;
                    Guid = g;
                    return true;
                }
                guidString = idWithoutPrefix.PadRight(padGuidToLength, '0');
                if (Guid.TryParse(guidString, out g))
                {
                    substringHigh = true;
                    Guid = g;
                    return true;
                }
                substringHigh = false;
                if (mustParse) throw new ArgumentException($"Unable to parse guid '{idWithoutPrefix}'", nameof(sfId));
                return false;
            }
        }
        #endregion
    }
}